ES6 Symbol,Iterator,Generator,Async,Class

ES6 中强大实用的语法有很多,例如:let关键字,const关键字,变量的解构赋值,模板字符串,简化对象,箭头函数,”…”拓展运算符,形参默认值,Promise 对象,Symbol 类型,Iterator 遍历器,Generator 函数,Async 函数,Class 等等。

前面说了 ES6 中的 Promise,今天来看看 Symbol,Iterator,Generator,Async,Class:

Symbol

ES5 中对象的属性名都是字符串,容易造成重名,污染环境,所以有了现在的 symbol~

概念:ES6 中的添加了一种原始数据类型 symbol (已有的原始数据类型:String, Number, boolean, null, undefined, 对象)

特点:

  1. Symbol 属性对应的值是唯一的,解决命名冲突问题
  2. Symbol 值不能与其他数据进行计算,包括同字符串拼串
  3. for in, for of遍历时不会遍历symbol属性。

使用方式:

  1. 调用Symbol函数得到symbol值:
1
2
3
let symbol = Symbol();
let obj = {};
obj[symbol] = 'hello';
  1. 传参标识
1
2
3
4
let symbol = Symbol('one');
let symbol2 = Symbol('two');
console.log(symbol);// Symbol('one')
console.log(symbol2);// Symbol('two')
  1. 内置Symbol值:除了定义自己使用的 Symbol 值以外,ES6 还提供了11个内置的 Symbol 值,指向语言内部使用的方法,其中最重要最常用的为:Symbol.iterator,对象的 Symbol.iterator 属性,指向该对象的默认遍历器方法。

使用案例:

1
2
3
4
5
6
7
8
9
10
11
12
let symbol = Symbol();
console.log(typeof symbol); //symbol
console.log(symbol); //Symbol()

// 用作对象的属性(唯一)
let obj = {username: 'kobe', age: 39};
obj[symbol] = 'hello';
obj[symbol] = 'symbol';
console.log(obj); // age:39,username:"kobe",Symbol():"symbol"
for(let i in obj){
console.log(i); //username age
}

Iterator 遍历器

概念: iterator 是一种接口机制,为各种不同的数据结构提供统一的访问机制。

作用:

  1. 为各种数据结构,提供一个统一的、简便的访问接口;
  2. 使得数据结构的成员能够按某种次序排列
  3. ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费。

工作原理:

  • 创建一个指针对象,指向数据结构的起始位置。
  • 第一次调用 next 方法,指针自动指向数据结构的第一个成员
  • 接下来不断调用 next 方法,指针会一直往后移动,直到指向最后一个成员
  • 每调用next方法返回的是一个包含 value 和 done 的对象,{value: 当前成员的值,done: 布尔值},其中,value表示当前成员的值,done 对应的布尔值表示当前的数据的结构是否遍历结束。并且当遍历结束的时候返回的 value 值是 undefined,done值为 true。

原生具备iterator接口的数据(可用for of遍历)有:Array、arguments、set容器、map容器、String等等。

使用方式:

自定义 iterator 生成指针对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function mockIterator(arr) {
let nextIndex = 0;
return {
next: function () {
return nextIndex<arr.length?{value: arr[nextIndex++], done: false}:{value: undefined, done: true}
}
}
}
let arr = [1,2,3];
let iteratorObj = mockIterator(arr);
console.log(iteratorObj.next()); //{value: 1, done: false}
console.log(iteratorObj.next()); //{value: 2, done: false}
console.log(iteratorObj.next()); //{value: 3, done: false}
console.log(iteratorObj.next()); //{value: undefined, done: true}

使用解构赋值以及…三点运算符时会调用iterator接口

1
2
3
let arr1 = [1,2,3,4,5];
let [value1, ...arr2] = arr1;
console.log(value1); //1

yield*语句:

1
2
3
4
5
6
7
8
function* generatorObj() {
yield '1'; // 可遍历数据,会自动调用iterator函数
yield '3';
}
let Go = generatorObj();
console.log(Go.next()); //{value: "1", done: false}
console.log(Go.next()); //{value: "3", done: false}
console.log(Go.next()); //{value: undefined, done: true}

原生数组:

1
2
3
4
let arr3 = [1, 2, 'kobe', true];
for(let i of arr3){
console.log(i); // 1 2 kobe true
}

字符串:

1
2
3
4
5
let str = 'abcdefg';
for(let item of str){
//a b c d e f g
console.log(item);
}

Generator 函数

概念:

  1. ES6 提供的解决异步编程的方案之一(Promise对象,Generator函数,Async函数)
  2. Generator 函数是一个状态机,内部封装了不同状态的数据
  3. 用来生成遍历器对象
  4. 可暂停函数(惰性求值), yield 可暂停,next 方法可启动。每次返回的是 yield 后的表达式结果

特点:

  1. function 与函数名之间有一个星号
  2. 内部用 yield 表达式来定义不同的状态
  3. generator 函数返回的是指针对象(利用 iterator 遍历器),而不会执行函数内部逻辑
  4. 调用 next 方法函数内部逻辑开始执行,遇到 yield 表达式停止,返回 {value: yield后的表达式结果/undefined, done: false/true}
  5. 再次调用next方法会从上一次停止时的yield处开始,直到最后
  6. yield 语句返回结果通常为 undefined, 当调用 next 方法时传参内容会作为启动时 yield 语句的返回值。

使用方式:

1
2
3
4
5
//内部用 yield 表达式来定义不同的状态
function* generatorExample(){
let result = yield 'hello'; // 状态值为hello
yield 'generator'; // 状态值为generator
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function* generatorTest() {
console.log('函数开始执行');
var result = yield 'hello';
console.log(result); //可获取传递来的参数,如果没有传递参数,则为 undefined
console.log('函数暂停后再次启动');
yield 'generator';
console.log("遍历完毕");
return "返回的结果";
}
// 生成遍历器对象
let Gt = generatorTest();
// 执行函数,遇到 yield 后即暂停
console.log(Gt); // 遍历器对象
let result = Gt.next(); // 函数执行,遇到 yield 暂停
console.log(result); // {value: "hello", done: false}
result = Gt.next("传递来的参数"); // 函数再次启动,并且传入参数
console.log(result); // {value: 'generator', done: false}
result = Gt.next();
console.log(result); //{value: "返回的结果", done: true} 表示函数内部状态已经遍历完毕
1
2
3
4
5
6
7
8
9
10
11
12
// 用对象的 Symbol.iterator 属性,指向遍历器对象,使对象可用 for of 遍历
let myIterable = {username:'kitty',age: 24}; //对象本不可以用 for of 遍历
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 4;
};
for(let i of myIterable){ //使用 for of 遍历的时候,默认使用Iterator接口
console.log(i);
}
let obj = [...myIterable];
console.log(obj);

使用案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
* 需求:
* 1、发送ajax请求获取新闻内容
* 2、新闻内容获取成功后再次发送请求,获取对应的新闻评论内容
* 3、新闻内容获取失败则不需要再次发送请求。
*
* */
function* sendXml() {
// url为next传参进来的数据
let url = yield getNews('http://localhost:3000/news?newsId=2');
yield getNews(url);
}
function getNews(url) {
$.get(url, function (data) {
console.log(data);
let commentsUrl = data.commentsUrl;
let url = 'http://localhost:3000' + commentsUrl;
// 当获取新闻内容成功,发送请求获取对应的评论内容
// 调用next传参会作为上次暂停是yield的返回值
sx.next(url);
})
}

let sx = sendXml();
// 发送请求获取新闻内容
sx.next();

上面是 Generator 函数版(引入jquery),下面我们对比一下上次的 Promise 版(原生js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//定义一个请求news的方法
function getNews(url) {
//创建一个promise对象
let promise = new Promise((resolve, reject) => {
//初始化promise状态为pending
//启动异步任务
let request = new XMLHttpRequest();
request.onreadystatechange = function () {
if(request.readyState === 4){
if(request.status === 200){
let news = request.response;
resolve(news);
}else{
reject('请求失败了。。。');
}
}
};
request.responseType = 'json';//设置返回的数据类型
request.open("GET", url);//规定请求的方法,创建链接
request.send();//发送
})
return promise;
}

getNews('http://localhost:3000/news?id=2')
.then((news) => {
console.log(news);
document.write(JSON.stringify(news));
console.log('http://localhost:3000' + news.commentsUrl);
return getNews('http://localhost:3000' + news.commentsUrl);
}, (error) => {
alert(error);
})
.then((comments) => {
console.log(comments);
document.write('<br><br><br><br><br>' + JSON.stringify(comments));
}, (error) => {
alert(error);
})

接下来我们还有 Async 版(引入jquery):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function sendXml(url) {
return new Promise((resolve, reject) => {
$.ajax({
url,
type: 'GET',
success: data => resolve(data),
error: error => reject(error) // error: error => resolve(false) 这样写可以用来报错给用户
})
})
}

async function getNews(url) {
let result = await sendXml(url);
//if(!result){alert("错误信息")}
let result2 = await sendXml('http://localhost:3000' + result.commentsUrl);
console.log(result, result2);
}
getNews('http://localhost:3000/news?id=2')

Async 函数(源自ES2017)

概念: 真正意义上去解决异步回调的问题,同步流程表达异步操作。本质是 Generator 的语法糖

语法:

1
2
3
4
async function foo(){
await 异步操作;
await 异步操作;
}

特点:

  1. 不需要像 Generator 去调用 next 方法,遇到 await 等待,当前的异步操作完成就往下执行
  2. 返回的总是 Promise 对象,可以用 then 方法进行下一步操作
  3. async 取代 Generator 函数的星号*,await 取代 Generator 的 yield
  4. 语意上更为明确,使用简单,经临床验证,暂时没有任何副作用

可以说,Async 函数是结合了 Promise 和 Generator 函数的使用。

使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//定义 async 函数
async function timeout(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms); //等于 setTimeout(function(){resolve();}, ms);
})
}

async function asyncPrint(value, ms) {
console.log('函数执行', new Date().toTimeString()); //函数执行 23:36:55 GMT+0800 (中国标准时间)
await timeout(ms); //等待异步函数执行完毕,这里为2秒后resolve()
console.log('延时时间', new Date().toTimeString()); //延时时间 23:36:57 GMT+0800 (中国标准时间)
console.log(value); // 'hello async'
}

console.log(asyncPrint('hello async', 2000));//Promise 对象

关于 Async 返回值:

普通函数返回的就是普通函数的返回值:

1
2
3
4
5
6
7
8
function test() {
return "xxx";
}
async function awaittest() {
let result = await test();
console.log(result); // "xxx"
}
awaittest();

Async 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function test() {
let a1 = await new Promise(function(resolve, reject) {
const num = 5;
resolve(num)
});
let a2 = await new Promise(function(resolve,reject){
//在这里拿到a1的返回值
console.log(a1) // 5
resolve(a1)
});

let a3 = await a2 //我想拿到a2的返回值
console.log(a2,a3) // 5 5 5
}

test()

Promise 需要把结果放在 resolve 里面而不是 return。

用 Class 定义类

在《你不知道的javascript》中有对 class 作如下解释:

你可能会认为 ES6 的class 语法是向 JavaScript 中引入了一种新的“类”机制,其实不是这样。class 基本上只是现有[[Prototype]](委托!)机制的一种语法糖。

也就是说,class 并不会像传统面向类的语言一样在声明时静态复制所有行为。如果你(有意或无意)修改或者替换了父“类”中的一个方法,那子“类”和所有实例都会受到影响,因为它们在定义时并没有进行复制,只是使用基于[[Prototype]] 的实时委托:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class C {
constructor() {
this.num = Math.random();
}
rand() {
console.log( "Random: " + this.num );
}
}
var c1 = new C();
c1.rand(); // "Random: 0.4324299..."
C.prototype.rand = function() {
console.log( "Random: " + Math.round( this.num * 1000 ));
};
var c2 = new C();
c2.rand(); // "Random: 867"
c1.rand(); // "Random: 432" ——噢!

使用步骤:

  1. 通过class定义类/实现类的继承
  2. 在类中通过constructor定义构造方法
  3. 通过new来创建类的实例
  4. 通过extends来实现类的继承
  5. 通过super调用父类的构造方法
  6. 重写从父类中继承的一般方法

使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person {
//调用类的构造方法
constructor(name, age){
this.name = name;
this.age = age;
}
//定义一般的方法
showName(){
console.log(this.name, this.age);
}
}
let person = new Person('kobe', 39);
console.log(person, person.showName());

//定义一个子类
class StrPerson extends Person{
constructor(name, age, salary){
super(name, age);//调用父类的构造方法
this.salary = salary;
}
showName(){//在子类自身定义方法
console.log(this.name, this.age, this.salary);
}
}
let str = new StrPerson('weide', 38, 1000000000);
console.log(str);
str.showName();



完~